﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Microsoft.Practices.Prism.ViewModel;

namespace Tanmia.Basis.Core
{
    /// <summary>
    /// Represent the base class for BusinessEntity in a Business Application.
    /// </summary>
    public class BaseBusinessObject : INotifyPropertyChanged, IDataErrorInfo, IValidatable
    {
        #region Private Fields
        private bool isValidatable;
        private int oldValidationResultCount = -1;
        private ObjectValidator validator;
        #endregion

        public BaseBusinessObject()
        {
            isValidatable = false;
            validator = new ObjectValidator(this);
        }

        #region IDataErrorInfo Members

        /// <summary>
        /// Gets an error message indicating what is wrong with this object.
        /// </summary>
        /// <returns>An error message indicating what is wrong with this object. The default is an empty string ("").</returns>
        public virtual string Error
        {
            get
            {
                if (!isValidatable)
                    return null;

                return validator.Error;
            }
        }

        /// <summary>
        /// Gets the error message for the property with the given name.
        /// </summary>
        /// <param name="memberName">The name of the property whose error message to get.</param>
        /// <returns>The error message for the property. The default is an empty string ("").</returns>
        public virtual string this[string columnName]
        {
            get
            {
                if (!isValidatable)
                    return null;

                RaisePropertyChanged(() => Error);

                return validator[columnName];
            }
        }

        #endregion

        #region IValidatable Members

        public virtual bool IsValid()
        {
            return string.IsNullOrEmpty(validator.Error);
        }

        public virtual ICollection<ValidationResult> Validate()
        {
            string tmp = validator.Error;
            ICollection<ValidationResult> retVals = validator.ValidationResults ?? new List<ValidationResult>();

            if (oldValidationResultCount != retVals.Count)
            {
                RaisePropertyChanged(() => Error);
                oldValidationResultCount = retVals.Count;
            }
            isValidatable = true;

            return retVals;
        }

        #endregion

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Raised when a property on this object has a new value.
        /// </summary>    
        public virtual event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises this object's PropertyChanged event.
        /// </summary>
        /// <param name="propertyName">The property that has a new value.</param>
        [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "Method used to raise an event")]
        protected virtual void RaisePropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        /// <summary>
        /// Raises this object's PropertyChanged event for each of the properties.
        /// </summary>
        /// <param name="propertyNames">The properties that have a new value.</param>
        [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "Method used to raise an event")]
        protected virtual void RaisePropertyChanged(params string[] propertyNames)
        {
            if (propertyNames == null) throw new ArgumentNullException("propertyNames");

            foreach (var name in propertyNames)
            {
                this.RaisePropertyChanged(name);
            }
        }

        /// <summary>
        /// Raises this object's PropertyChanged event.
        /// </summary>
        /// <typeparam name="T">The type of the property that has a new value</typeparam>
        /// <param name="propertyExpression">A Lambda expression representing the property that has a new value.</param>
        [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "Method used to raise an event")]
        [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Cannot change the signature")]
        protected virtual void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression)
        {
            var propertyName = PropertySupport.ExtractPropertyName(propertyExpression);
            this.RaisePropertyChanged(propertyName);
        }

        /// <summary>
        /// Check if the value is not variable then set the value in the variable and raise the Notification event.
        /// </summary>
        /// <typeparam name="T">The type of the variable and value</typeparam>
        /// <param name="propertyName">Property name which its value have been changed.</param>
        /// <param name="variable">Inner filed of the property.</param>
        /// <param name="value">New value of the property.</param>
        protected virtual void SetValue<T>(string propertyName, ref T variable, T value)
        {
            if (variable == null || !variable.Equals(value))
            {
                variable = value;
                RaisePropertyChanged(propertyName);
            }
        }
        #endregion
    }
}
